package middleware

import (
	"errors"
	"fmt"
	"reflect"

	"github.com/gin-gonic/gin"
	"github.com/go-playground/validator/v10"
	"github.com/lab-online/pkg/color"
	"github.com/lab-online/pkg/resp"
	"github.com/lab-online/pkg/util"
)

// ValidatorOptions Todo interface{} 约束为指针类型
type ValidatorOptions struct {
	BodyKey string
	Body    any

	ParamsKey string
	Params    any

	QueryKey string
	Query    any

	HeaderKey string
	Header    any
}

const (
	KeyBody   = "body"
	KeyParams = "params"
	KeyQuery  = "query"
	KeyHeader = "header"
)

type validatorMap map[string]interface{}
type validatorKeyMap map[string]string
type ValidatorOptionBuilder func() *ValidatorOptions

// NewValidator 创建验证器中间件
func NewValidator(optsFn ValidatorOptionBuilder) gin.HandlerFunc {
	optionKey := setupValidator(optsFn)

	return func(c *gin.Context) {
		validators := buildValidatorMap(optsFn)

		for key, obj := range validators {
			var err error
			switch key {
			case KeyBody:
				err = c.ShouldBindJSON(obj)
			case KeyParams:
				err = c.ShouldBindUri(obj)
			case KeyQuery:
				err = c.ShouldBindQuery(obj)
			case KeyHeader:
				err = c.ShouldBindHeader(obj)
			}

			if err != nil {
				handleValidatorError(c, err)
				return
			} else {
				c.Set(optionKey[key], obj)
			}
		}

		c.Next()
	}
}

// setupValidator 设置验证器的key, 并检查输入的验证器是否为指针类型
func setupValidator(builder ValidatorOptionBuilder) validatorKeyMap {
	for _, obj := range buildValidatorMap(builder) {
		val := reflect.ValueOf(obj)
		if val.Kind() != reflect.Ptr {
			panic(color.PrefixFatal + "Validator middleware options must be pointer")
		}
	}

	opts := builder()
	return validatorKeyMap{
		KeyBody:   util.ConditionalPicker(opts.BodyKey == "", KeyBody, opts.BodyKey),
		KeyParams: util.ConditionalPicker(opts.ParamsKey == "", KeyParams, opts.ParamsKey),
		KeyQuery:  util.ConditionalPicker(opts.QueryKey == "", KeyQuery, opts.QueryKey),
		KeyHeader: util.ConditionalPicker(opts.HeaderKey == "", KeyHeader, opts.HeaderKey),
	}

}

// buildValidatorMap 构建需要验证的结构体
func buildValidatorMap(builder ValidatorOptionBuilder) validatorMap {
	opts := builder()
	res := validatorMap{}

	if opts.Body != nil {
		res[KeyBody] = opts.Body
	}
	if opts.Params != nil {
		res[KeyParams] = opts.Params
	}
	if opts.Query != nil {
		res[KeyQuery] = opts.Query
	}
	if opts.Header != nil {
		res[KeyHeader] = opts.Header
	}

	return res
}

// handleValidatorError 处理验证错误
func handleValidatorError(c *gin.Context, err error) {
	var validationErrs validator.ValidationErrors
	if errors.As(err, &validationErrs) {
		errMsg := map[string]string{}
		for _, e := range validationErrs {
			key := e.StructNamespace()
			errMsg[key] = fmt.Sprintf("validate failed: %s", e.ActualTag())
		}
		resp.BadRequest(c, resp.CodeValidateError, errMsg)
	} else {
		resp.BadRequest(c, resp.CodeValidateError, err.Error())
	}
}
